package id;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;

import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECPoint;

class Sector {
    String identifier;
    String userID;
    private Certificate certificate;
    ECPoint sectorKey;
    ECPoint sectorPublicKey;

    Sector(String identifier, String userID, ECPoint gen, BigInteger privateKey) {
        this.identifier = identifier;
        this.userID = userID;
        this.sectorPublicKey = gen;
        sectorKey = gen.multiply(privateKey); // p(A)B - na stałe
    }

    void setCertificate(CertificateAuthority CA, Card card) throws Exception {
        certificate = CA.generateCertificate(card, identifier);
    }

    Certificate getCertificate() {
        return certificate;
    }
}

public class Card {
    private String user;
    private SecureRandom secureRandom;
    private ECNamedCurveParameterSpec parameters = ECNamedCurveTable.getParameterSpec("secp256r1");
    private BigInteger modulo;
    private BigInteger privateKey;
    static int keyLength = 256;

    private HashMap<String, Sector> sectors;

    public Card(String user) {
        this.user = user;
        secureRandom = new SecureRandom();
        modulo = parameters.getN();
        privateKey = new BigInteger(keyLength, secureRandom).mod(modulo);
        sectors = new HashMap<String, Sector>();
    }

    public Certificate getCertificate(String sectorID) {
        return sectors.get(sectorID).getCertificate();
    }

    public void registerForSector(String sectorID, CertificateAuthority CA) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(sectorID.getBytes());
        byte[] hashedMessage = md.digest(user.getBytes());
        String userID = (new BigInteger(hashedMessage)).toString();

        Sector A = new Sector(sectorID, userID, Hash.hash(sectorID, parameters), privateKey);
        sectors.put(sectorID, A);
        A.setCertificate(CA, this);
    }

    public ECPoint getSectorKey(String sectorID) {
        return sectors.get(sectorID).sectorKey;
    }

    public String getSectorUserID(String sectorID) {
        return sectors.get(sectorID).userID;
    }

    public HashMap<String, Object> sign(String message, String sectorID) throws NoSuchAlgorithmException {
        Sector A = sectors.get(sectorID);
        BigInteger r = new BigInteger(keyLength, secureRandom);
        ECPoint R = A.sectorPublicKey.multiply(r); // R = [r]g(A)

        MessageDigest md = MessageDigest.getInstance("SHA-256");

        // H(g(A) || p(A)B || R || message
        String hashArgument = A.sectorPublicKey.toString().concat(A.sectorKey.toString()).concat(R.toString()).concat(message);

        byte[] hashedMessage = md.digest(hashArgument.getBytes());
        BigInteger hashedValue = new BigInteger(hashedMessage).abs();
        // S = H * x_ab + r mod q
        BigInteger S = hashedValue.multiply(privateKey).add(r).mod(modulo);
        HashMap<String, Object> signature = new HashMap<String, Object>();
        signature.put("S", S);
        signature.put("R", R);
        signature.put("message", message);
        return signature;
    }

    public boolean verify(HashMap<String, Object> signature, String sectorID) throws NoSuchAlgorithmException {
        Sector A = sectors.get(sectorID);
        BigInteger S = (BigInteger) signature.get("S");
        ECPoint R = (ECPoint) signature.get("R");
        String message = (String) signature.get("message");

        // H(g(A) || p(A)B || R || message
        String hashArgument = A.sectorPublicKey.toString().concat(A.sectorKey.toString()).concat(R.toString()).concat(message);

        MessageDigest md = MessageDigest.getInstance("SHA-256");

        byte[] hashedMessage = md.digest(hashArgument.getBytes());
        BigInteger H = new BigInteger(hashedMessage).abs();
        ECPoint left = A.sectorPublicKey.multiply(S);
        ECPoint right = A.sectorKey.multiply(H).add(R);
        return left.equals(right);
    }

    protected BigInteger computeM(BigInteger r, BigInteger c) {
        return r.add(c.multiply(privateKey)).mod(modulo);
    }
}
